import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.Security;
import java.util.Arrays;
import java.util.Random;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

import org.bouncycastle.jce.provider.BouncyCastleProvider;


public class SimuladorClientes {

	private final static int PORT_SERVER = 8888;


	//private final static String PASS="c1";
	//private final static String USR="c1@gmail.com"; 

	private final static int USR=0;
	private final static int PASS=1;


	public static final int MINUTO = 60000; // minuto en milisegundos
	public static final int SEGUNDO = 1000; // segundo en milisegundos

	// flag para establecer si cada cliente se conectara en algun momento aleatorio dentro de un periodo de 5 minutos 
	private static boolean flag_conexion_azar; 

	// flag para establecer si el cliente emite su voto en un momento aleatorio dentro de un perido de ... minutos
	private static boolean flag_votacion_azar;

	// flag para establecer si el cliente puede llegar a desconectarse en alg�n momento dentro de los primeros 10 minutos de establecida la conexi�n
	private static boolean flag_desconexion;


	// cant de asambleistas a conectar (entre 0 y 72)
	private static int cant_a_conectar;
	private static int modo_aleatorio;
	//private SSLSocket socket_cliente;

	private String ip_server;
	private Integer conexiones_exitosas;
	private SimuladorClientes principal;

	public static void main(String[] args){
		// leer argumentos de entrada

		if(args.length==1){
			if(args[0].toUpperCase().equals("-H")){
				mostrarAyuda();
				System.exit(0);
			}else{
				System.err.println("Parametro inválido\n");
				mostrarAyuda();
				System.exit(0);
			}

		}else if(args.length == 4){
			if(args[0].toUpperCase().equals("-CANT") && args[2].toUpperCase().equals("-AZAR")){
				try{
					cant_a_conectar= Integer.parseInt(args[1]);
					modo_aleatorio=Integer.parseInt(args[3]);
					flag_desconexion=false;

					if(cant_a_conectar<0 || cant_a_conectar>72){
						System.err.println("Valor no permitido para la cantidad de asambleistas\n");
						mostrarAyuda();
						System.exit(0);
					}

					if(modo_aleatorio<0 || modo_aleatorio>3){
						System.err.println("Valor no permitido para el modo aleatorio\n");
						mostrarAyuda();
						System.exit(0);
					}


				}catch (NumberFormatException e) {
					System.err.println("Parametro númerico inválido\n");
					mostrarAyuda();
					System.exit(0);
				};
			}else{
				System.err.println("Parametro inválido");
				System.exit(0);
			}
		}else if(args.length == 5){
			if(args[0].toUpperCase().equals("-CANT") && args[2].toUpperCase().equals("-AZAR") && args[4].toUpperCase().equals("-D")){
				try{
					cant_a_conectar= Integer.parseInt(args[1]);
					modo_aleatorio=Integer.parseInt(args[3]);
					flag_desconexion=true;
					if(cant_a_conectar<0 || cant_a_conectar>72){
						System.err.println("Valor no permitido para la cantidad de asambleistas\n");
						mostrarAyuda();
						System.exit(0);
					}

					if(modo_aleatorio<0 || modo_aleatorio>3){
						System.err.println("Valor no permitido para el modo aleatorio\n");
						mostrarAyuda();
						System.exit(0);
					}

				}catch (NumberFormatException e) {
					System.err.println("Parametro númerico inválido\n");
					mostrarAyuda();
					System.exit(0);
				};
			}else{
				System.err.println("Parametro inválido\n");
				mostrarAyuda();
				System.exit(0);
			}			
		}else{
			// cant parametros invalida
			System.err.println("Cantidad de parametros inválido\n");
			mostrarAyuda();
			System.exit(0);
		}




		try {
			File f = new File("asambleistas.txt");
			BufferedReader bf = new BufferedReader(new FileReader(f));
			// leer ip de archivo
			String ip = bf.readLine();

			// leer cant de usuarios
			int cant = Integer.parseInt(bf.readLine());

			// leer tuplas usr - pass
			String[][] usr_pass = new String[cant][2];
			for(int i=0; i<cant; i++){
				String linea = bf.readLine();
				String[] split = linea.split("#");
				usr_pass[i][USR]= split[USR];
				usr_pass[i][PASS]= split[PASS];
			}

			new SimuladorClientes().conectar(usr_pass, ip);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (NumberFormatException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}




	}

	public void conectar(String[][] asambleistas, String ip){
		
		conexiones_exitosas=0;
		principal=this;
		System.out.println("Se inicia el simulador de clientes con los siguiente parametros:\n\t. Cant de asambleistas: "+cant_a_conectar);
		System.out.println("\t. IP del servidor: "+ip);
		switch(modo_aleatorio){
		case 0:{
			flag_conexion_azar=false; 
			flag_votacion_azar=false;
			System.out.println("\t. Conexion aleatoria: DESACTIVADO\n\t. Votacion aleatoria: DESACTIVADO");
			break;
		}
		case 1:{
			flag_conexion_azar=true; 
			flag_votacion_azar=false;
			System.out.println("\t. Conexion aleatoria: ACTIVADO\n\t. Votacion aleatoria: DESACTIVADO");
			break;
		}
		case 2:{
			flag_conexion_azar=false; 
			flag_votacion_azar=true; 
			System.out.println("\t. Conexion aleatoria: DESACTIVADO\n\t. Votacion aleatoria: ACTIVADO");
			break;
		}
		case 3:{
			flag_conexion_azar=true; 
			flag_votacion_azar=true; 
			System.out.println("\t. Conexion aleatoria: ACTIVADO\n\t. Votacion aleatoria: ACTIVADO");
			break;
		}

		}
		if(flag_desconexion){
			System.out.println("\t. Desconexión aleatoria de clientes: ACTIVADO");
		}else{
			System.out.println("\t. Desconexión aleatoria de clientes: DESACTIVADO");
		}

		ip_server=ip;


		final ContadorConectados contadorConectados = new ContadorConectados();
		final GeneradorVotos generadorVotos = new GeneradorVotos(contadorConectados);

		try {
			Security.addProvider(new BouncyCastleProvider());
			for(int i=0; i<cant_a_conectar; i++){
				
				// se obtiene una instancia de keystore del tipo BKS y luego se lo asocia al keystore del directorio raw
				final String usr=asambleistas[i][USR];
				final String pass=asambleistas[i][PASS];
				//System.err.println(usr+" "+pass);
				Runnable runnable = new Runnable(){
					public void run(){
						try {
							if(flag_conexion_azar){// si el flag est� activado, espera cierta cant de minutos antes de conectarse
								Random random = new Random();
								int min_espera= random.nextInt(MINUTO);
								System.out.println("El cliente "+usr+" espera "+(min_espera/1000)+" segundos antes de iniciar conexion");
								try{Thread.sleep(min_espera);}catch(Exception e){};
							}
							KeyStore key_store = KeyStore.getInstance("BKS");
							//InputStream keystoreStream = getClass().getClassLoader().getResourceAsStream("keystore/"+usr+".bks");
							File file = new File("keystores/"+usr+".bks");
							InputStream keystoreStream = new FileInputStream(file);
							//InputStream keystoreStream = getClass().getClassLoader().getResourceAsStream("keystores/"+usr+".bks");
							if(keystoreStream==null)
								System.err.println("NO SE ENCONTRO EL BKS DE: "+usr);
							else{

								// se hace un load del keystore
								key_store.load(keystoreStream, pass.toCharArray());
								// asociamos el keystore a una instancia de keymanager para gestionar las claves p�blica y privadas
								KeyManagerFactory key_manager = KeyManagerFactory.getInstance("SunX509");
								char[] passKey = (pass.length()<7)? pass.toCharArray(): Arrays.copyOfRange(pass.toCharArray(), 0, 7);
								key_manager.init(key_store, passKey);// no era necesario el usario???

								// para gestionar los certificados del keystore, lo asociamos a un trust-storemanager
								TrustManagerFactory trust_manager = TrustManagerFactory.getInstance("SunX509");
								trust_manager.init(key_store);

								// con una instancia de SSLContext establecemos las opciones del socket que luego generaremos
								SSLContext ssl_context = SSLContext.getInstance("TLS");

								// IMPORTANTE: ac� establecemos el keymanager y el truststore local del socket ssl
								ssl_context.init(key_manager.getKeyManagers(), trust_manager.getTrustManagers(), null);

								// finalmente obtenemos la instancia del socket ssl cliente que usaremos para conectarnos al servidor
								SSLSocketFactory ssl_factory = ssl_context.getSocketFactory();

								// creo una instancia de socket ssl para con conexi�n al servidor
								final SSLSocket socket_cliente = (SSLSocket) ssl_factory.createSocket(ip_server, PORT_SERVER); 
								socket_cliente.startHandshake();
								System.out.println("El cliente "+usr+" se conecto correctamente al servidor");
								principal.verificarConexionesExitosas();
								
								
								//System.err.println("CORTAR: "+cort);
								new ClienteTest(socket_cliente,usr,flag_votacion_azar, contadorConectados, generadorVotos).simular(flag_desconexion);
							}
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
							System.err.println("Problema con el cliente "+usr+": "+e.getMessage());
						}
					}


				};
				new Thread(runnable).start();
	


			}//for
			//}
		}catch (Exception e) {
			e.printStackTrace();
		}


	}
	
	protected synchronized void verificarConexionesExitosas(){
		conexiones_exitosas++;
		if(conexiones_exitosas==cant_a_conectar){
			System.out.println("TODOS LOS CLIENTES SE CONECTARON EXITOSAMENTE CON EL SERVIDOR");
		}
	}
	
	private static void mostrarAyuda(){
		System.out.println("tester [-h] [-cant cant_asambleistas -azar valor_azar [-d]]\n\n-h\n\tMuestra la ayuda.\n\n-cant\n\tEstablece la cantidad de asambleistas que se conectaran al sistema. Valores v�lidos dentro de rango de [0 .. 72]\n\n-azar\n\tPermite establecer ciertos comportamientos aleatorios de los clientes. Valores entre [0 .. 3]\n\t\t0 Nada se lleva a cabo de manera aleatoria.[NO recomendado, demasiados 'handshakes' a la vez hace que muchos clientes no sean aceptados]\n\t\t1 La conexi�n se establece dentro de los primeros 7 minutos (de manera aleatoria para cada cliente).\n\t\t2 El voto se emite en un momento aleatorio (dentro del tiempo establecido para eso).[No recomendado por misma raz�n que opci�n 0]\n\t\t3 Tanto la conexi�n como el voto se realizan en un momento aleatorio.\n\n-d\n\tEstablece si algunos de los clientes (seleccionados aleatoriamente) se desconectaran en alg�n momento dentro de los primeros 7 minutos de establecida la conexi�n.");
	}

}
